package com.ringtheweb.serviceprovider.weather.core;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ringtheweb.serviceprovider.weather.exception.IllegalLocationException;
import com.ringtheweb.serviceprovider.weather.model.Location;

import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

/**
 * Base implementation of {@link ILocationRetriever}
 * @author Luc
 *
 * @param <LocationType>
 */
public abstract class AbstractHttpLocationRetriever implements ILocationRetriever {
	
	private String baseXPath ;
	
	private static final String ALPHABET_INDEX="//div[@id='yw-regionalnav']/ul/li/a";
	
	private Log LOG = LogFactory.getLog(AbstractHttpLocationRetriever.class);
	
	private int DELAY = 1000;
	
	private long MAX_COUNT = 10;
	
	public AbstractHttpLocationRetriever(String xPath) {
		this.baseXPath =xPath;
	}

	public List<Location> retrieveChildren(Location parentLocation) {
		        
        List<Location> locations = new ArrayList<Location>();
       
        int count = 0;
        boolean retrieved = false;
        
        do {
            try {
            	retrieved = retrieveLocationsFrom(locations,parentLocation.getUrl(),0,(int)Math.sqrt((count+1))*DELAY);
            }
            catch (HttpRetrievalRetryException e) {
        		LOG.warn("Site blocked, retrying"+parentLocation.getUrl(),e);
            }
            count++;
            if (count >= MAX_COUNT) {
            	LOG.warn("Maximum count reached, stopping retrieval of="+parentLocation.getUrl());
            	retrieved = true;
            }
        }
        while(! retrieved); 

        for (Location childLocation: locations) {
        	try {
        		parentLocation.addChildLocation(childLocation);
        	}
        	catch(IllegalLocationException e) {
        		LOG.error("Trying to add an incorrect location, mi");
        	}
        	
        }
        
        return locations;

			
	}
	
	private boolean retrieveLocationsFrom(List<Location> locations,String searchUrl,int pageIdx,int delay) throws HttpRetrievalRetryException {
		try {
	        final WebClient webClient = new WebClient();
	        Thread.sleep(delay);
	        if (LOG.isDebugEnabled()) LOG.debug("Retrieving page="+searchUrl);
	        final HtmlPage page = (HtmlPage) webClient.getPage(searchUrl);
	        
	        List<HtmlAnchor> nodes = (List<HtmlAnchor>)page.getByXPath(getBaseXPath());
	       
	        for (HtmlAnchor anchor: nodes) {
	        	URL url = new URL(returnBaseURL(new URL(searchUrl))+anchor.getHrefAttribute());
	        	locations.add(new Location(anchor.asText(),getTransition().getTo(),url));
	        }
	        
	        int nextIdx = pageIdx +1;
	        
	        List<HtmlAnchor> alphabetLst  = null ;
	        try {
	        	Thread.sleep(delay);
		        alphabetLst = (List<HtmlAnchor>)page.getByXPath(ALPHABET_INDEX);
		        if (alphabetLst.size() > nextIdx) {
		        	retrieveLocationsFrom(locations, 
		        		returnBaseURL(new URL(searchUrl))+alphabetLst.get(nextIdx).getHrefAttribute(), nextIdx, delay);
		        }
			        
	        }
	        catch(ClassCastException e) {
	        	LOG.warn("No index table found");
	        }
	        	
		}
		catch(com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException e) {
			if( e.getStatusCode() == 999) {
				throw new HttpRetrievalRetryException("An 999 error occured retrieving page="+searchUrl,e);
			}
		}
		// Unrecoverable error, no need to go on
		catch(Throwable t) {
			LOG.error("An error occured retrieving locations in page="+searchUrl,t);
		}
		return true;
		
	}
	
	public String getBaseXPath() {
		return baseXPath;
	}

	public void setBaseXPath(String baseXPath) {
		this.baseXPath = baseXPath;
	}
	
	
	private String returnBaseURL(URL url) {
		StringBuilder sb = new StringBuilder();
		sb.append(url.getProtocol());
		sb.append("://");
		sb.append(url.getHost());
		if (url.getDefaultPort() != url.getPort() && url.getPort() != -1) {
			sb.append(":");
			sb.append(url.getPort());
		}
		return sb.toString();
	}
	
	
	

}
